/*                                    _
 *   ___ _ __ ___ _ __ ___   ___   __| |
 *  / __| '__/ __| '_ ` _ \ / _ \ / _` |
 *  \__ \ |  \__ \ | | | | | (_) | (_| |
 *  |___/_|  |___/_| |_| |_|\___/ \__,_|     
 * 
 *	_|_ _  _ |    _ _ . _ _ _  _    _  
 *	 | (_|| ||<  | | ||_\_\|_)(_|VV| | 
 *	                       |           
 *			  _ _  _  _|   | _ 
 *			 | | |(_)(_||_||(/_
 *
 ******************************************
 ******************************************
 ******************************************
 * . _ _  _ | _  _ _  _  _ _|_ _ _|_. _  _ 
 * || | ||_)|(/_| | |(/_| | | (_| | |(_)| |
 *       |                                 
 *		
 *		Uses Tank Spawn forward provided by Left4Downtown and determines if
 *		chosen Tank Spawn Coordinates are reachable by Survivors. If they're
 *		not, block Spawn for some time and try again later.
 *	
 *		Since the Reachability SDK Call only works on Survivor Bots, we have to
 *		do some real magic to get (and FIND) one without triggering score or anything else.
 *		Ultimately, the solution was to swap a human to Spec for one Frame.
 *	
 */

#define MODULE_NAME "TankMisspawn"

static const String:GAMECONFIG_FILE[]			= "srsmod";
static const String:GAMECONFIG_FILE_L4DT[]		= "left4downtown.l4d2";
static const String:IS_REACHABLE_SIGNATURE[]	= "IsReachable_Vector";

static const Float:SPAWN_RETRY_DELAY			= 5.0;

static Handle:cvarTankSpawnCheckEnabled			= INVALID_HANDLE;
static Handle:sdkcallIsReachable				= INVALID_HANDLE;
static Handle:sdkcallSetHumanSpec				= INVALID_HANDLE;
static Handle:sdkcallTakeOverBot				= INVALID_HANDLE;

static bool:isModuleOn							= false;
static bool:spawnTimeOut						= false;

public TankMisspawn_OnModuleLoaded()
{
	cvarTankSpawnCheckEnabled = 		CreateConVar("srs_tank_misspawn_fix",		"1", 	"Enable tank mis-spawn precautions.",		SRS_CVAR_DEFAULT_FLAGS);
	
	PrepSDKCalls();

	_TM_OnModuleEnabled(); // default ON
	HookConVarChange(cvarTankSpawnCheckEnabled, _TM_FlipActivation);
	
	TankSwapBugFix();
}

public _TM_FlipActivation(Handle:convar, const String:oldValue[], const String:newValue[])
{
	if (ACTIVATION_FLIP_OFF_TO_ON)
	{
		_TM_OnModuleEnabled();
	}
	else if (ACTIVATION_FLIP_ON_TO_OFF)
	{
		_TM_OnModuleDisabled();
	}
}

static _TM_OnModuleEnabled()
{
	isModuleOn = true;
}

static _TM_OnModuleDisabled()
{
	if (!IsPluginEnding())
	{
		isModuleOn = false;
	}
}

public Action:L4D_OnSpawnTank(const Float:vector[3], const Float:qangle[3])
{
	if (isModuleOn && IsAllowedGameMode() && !L4D2_IsFinaleMapNow())
	{
		if (spawnTimeOut)
		{
			return Plugin_Handled;
		}
	
		Debug_Print("L4D_OnSpawnTank fired, checking Spawning Spot for [Bot] reachability");
		
		new bot = -1;
		FOR_EACH_ALIVE_SURVIVOR(i)
		{
			if (IsFakeClient(i))
			{
				bot = i;
				break;
			}
		}
		
		new bool:isReachable = true;
		
		if (bot == -1)
		{
			new human = -1;
			FOR_EACH_ALIVE_SURVIVOR(i)
			{
				if (!IsFakeClient(i))
				{
					human = i;
					Debug_Print("No Bot present, swapping %N to Spec to get one", i);
					ChangeClientTeam(i, 1);
					break;
				}
			}
			
			FOR_EACH_ALIVE_SURVIVOR(i)
			{
				if (IsFakeClient(i))
				{
					bot = i;
					break;
				}
			}
			
			if (bot == -1)
			{
				Debug_Print("Cant find Bot after swapping Human to Spec");
			}
			else
			{
				isReachable = IsReachableBySurvivorBot(bot, vector);
				
				Debug_Print("Now attempting to get the human back into his Survivor");
				SDKCall(sdkcallSetHumanSpec, bot, human);
				SDKCall(sdkcallTakeOverBot, human, true);
			}
		}
		else
		{
			isReachable = IsReachableBySurvivorBot(bot, vector);
		}
		
		if (isReachable)
		{
			Debug_Print("Tank Spawn deemed reachable, continue");
		}
		else
		{
			Debug_Print("Tank Spawn appears unreachable, delaying Spawn by %f seconds", SPAWN_RETRY_DELAY);
			CreateTimer(SPAWN_RETRY_DELAY, _TM_ResetDelay_timer);
			spawnTimeOut = true;
			return Plugin_Handled;
		}
	}
	
	return Plugin_Continue;
}

public Action:_TM_ResetDelay_timer(Handle:timer)
{
	spawnTimeOut = false;
}

static PrepSDKCalls()
{
	new Handle:ConfigFile = LoadGameConfigFile(GAMECONFIG_FILE);

	StartPrepSDKCall(SDKCall_Player);
	PrepSDKCall_SetFromConf(ConfigFile, SDKConf_Signature, IS_REACHABLE_SIGNATURE);
	PrepSDKCall_AddParameter(SDKType_Vector, SDKPass_Plain);
	PrepSDKCall_SetReturnInfo(SDKType_PlainOldData, SDKPass_Plain);
	sdkcallIsReachable = EndPrepSDKCall();
	CloseHandle(ConfigFile);
	
	if (sdkcallIsReachable == INVALID_HANDLE)
	{
		SetFailState("Cant initialize SurvivorBot::%s SDKCall", IS_REACHABLE_SIGNATURE);
	}
	
	ConfigFile = LoadGameConfigFile(GAMECONFIG_FILE_L4DT);
	
	StartPrepSDKCall(SDKCall_Player);
	PrepSDKCall_SetFromConf(ConfigFile, SDKConf_Signature, "SetHumanSpec");
	PrepSDKCall_AddParameter(SDKType_CBasePlayer, SDKPass_Pointer);
	sdkcallSetHumanSpec = EndPrepSDKCall();
	
	if (sdkcallSetHumanSpec == INVALID_HANDLE)
	{
		SetFailState("Cant initialize SetHumanSpec SDKCall");
	}
	
	StartPrepSDKCall(SDKCall_Player);
	PrepSDKCall_SetFromConf(ConfigFile, SDKConf_Signature, "TakeOverBot");
	PrepSDKCall_AddParameter(SDKType_Bool, SDKPass_Plain);
	sdkcallTakeOverBot = EndPrepSDKCall();
	
	if (sdkcallTakeOverBot == INVALID_HANDLE)
	{
		SetFailState("Cant initialize TakeOverBot SDKCall");
	}
}

// SurvivorBot::IsReachable(Vector  const&)const
static bool:IsReachableBySurvivorBot(target, const Float:vector[3])
{
	Debug_Print("SurvivorBot::IsReachable(Vector  const&) being called, target %N", target);
	return SDKCall(sdkcallIsReachable, target, vector);
}

static TankSwapBugFix()
{
	if (FindConVar("l4d2_tankswap_version") != INVALID_HANDLE)
	{
		ServerCommand("sm plugins unload l4d2_tankswap.smx");
		ServerCommand("sm plugins load l4d2_tankswap.smx");
	}
}

#undef MODULE_NAME